package com.varian.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.varian.mybatis.base.BaseServiceImpl;
import com.varian.mybatis.util.PageUtil;
import com.varian.security.constant.SecurityConstant;
import com.varian.security.model.LoginUser;
import com.varian.security.properties.VarianSecurityProperties;
import com.varian.security.util.SecurityUtil;
import com.varian.service.util.ParamUtil;
import com.varian.system.dto.UserDetailDTO;
import com.varian.system.dto.UserLoginDTO;
import com.varian.system.constant.SystemConstant;
import com.varian.system.convert.SystemConvert;
import com.varian.system.dto.*;
import com.varian.system.entity.SysDept;
import com.varian.system.entity.SysMenu;
import com.varian.system.entity.SysRole;
import com.varian.system.entity.SysUser;
import com.varian.system.mapper.*;
import com.varian.system.service.ISysUserService;
import com.varian.system.vo.SysUserGrantVO;
import com.varian.system.vo.SysUserProfileVO;
import com.varian.system.vo.SysUserVO;
import com.varian.tool.exception.BizException;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
import java.util.Set;

/**
 * @author ben
 * @since 2024/7/1
 */
@Service
@RequiredArgsConstructor
@EnableConfigurationProperties(VarianSecurityProperties.class)
public class SysUserServiceImpl extends BaseServiceImpl<SysUserMapper, SysUser> implements ISysUserService {

    private final SystemConvert baseConvert;
    private final PasswordEncoder passwordEncoder;
    private final VarianSecurityProperties securityProperty;
    private final UserRoleMapper userRoleMapper;
    private final SysRoleMapper roleMapper;
    private final SysMenuMapper menuMapper;
    private final SysDeptMapper deptMapper;

    @Override
    public boolean add(SysUserDTO dto) {
        SysDept dept = deptMapper.selectById(dto.getDeptId());
        Assert.notNull(dept, () -> BizException.of(SystemConstant.DEPT_NOT_EXIST));
        SysUser user = baseConvert.toEntity(dto);
        validateUniqueness(user);
        String defaultPassword = ParamUtil.getStr("USER_DEFAULT_PASSWORD");
        user.setPassword(passwordEncoder.encode(defaultPassword));
        return save(user);
    }

    @Override
    public boolean modify(Long id, SysUserDTO dto) {
        SysDept dept = deptMapper.selectById(dto.getDeptId());
        Assert.notNull(dept, () -> BizException.of(SystemConstant.DEPT_NOT_EXIST));
        validateExistence(id);
        SysUser user = baseConvert.toEntity(id, dto);
        validateUniqueness(user);
        return updateById(user);
    }

    @Override
    public SysUserVO detail(Long id) {
        SysUser user = validateExistence(id);
        return baseConvert.toVO(user);
    }

    @Override
    public Page<SysUserVO> page(SysUserPageDTO dto) {
        Page<SysUser> userPage = baseMapper.selectPage(dto.toPage(), lambdaQuery().getWrapper()
                .eq(dto.getDeptId() != null, SysUser::getDeptId, dto.getDeptId())
                .and(StrUtil.isNotEmpty(dto.getKeyword()), w -> w.likeRight(SysUser::getUsername, dto.getKeyword()).or().likeRight(SysUser::getNickname, dto.getKeyword()))
                .eq(dto.getUserStatus() != null, SysUser::getUserStatus, dto.getUserStatus())
                .orderByDesc(SysUser::getId)
        );
        return PageUtil.convert(userPage, baseConvert::toVO);
    }

    @Override
    public SysUserGrantVO grantedList(Long id) {
        validateExistence(id);
        return new SysUserGrantVO().setRoleIds(userRoleMapper.selectRoleIdsByUserId(id));
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean grant(Long id, SysUserGrantDTO dto) {
        validateExistence(id);
        userRoleMapper.deleteByUserId(id);
        return CollUtil.isEmpty(dto.getRoleIds()) || SqlHelper.retBool(userRoleMapper.insertBatch(id, dto.getRoleIds()));
    }

    @Override
    public UserDetailDTO getUserByUsername(String username) {
        Optional<SysUser> userOpt = getOneOpt(lambdaQuery().getWrapper().eq(SysUser::getUsername, username));
        return userOpt.map(user -> {
            Set<String> permissionCodes = CollUtil.newHashSet();
            List<SysRole> roles = roleMapper.selectListByUserId(user.getId());
            if (CollUtil.isNotEmpty(roles)) {
                roles.forEach(r -> permissionCodes.add(SecurityConstant.ROLE_PREFIX + r.getRoleCode()));
                List<Long> roleIds = CollUtil.map(roles, SysRole::getId, true);
                List<SysMenu> menus = menuMapper.selectListByRoleIds(roleIds);
                CollUtil.forEach(menus, (m, index) -> permissionCodes.add(m.getPermissionCode()));
            }
            return new UserDetailDTO()
                    .setId(user.getId())
                    .setDeptId(user.getDeptId())
                    .setUsername(user.getUsername())
                    .setNickname(user.getNickname())
                    .setAvatar(user.getAvatar())
                    .setPassword(user.getPassword())
                    .setUserStatus(user.getUserStatus())
                    .setPermissionCodes(permissionCodes);
        }).orElse(null);
    }

    @Override
    public SysUserProfileVO userProfile() {
        LoginUser loginUser = SecurityUtil.getLoginUser();
        SysUser user = baseMapper.selectById(loginUser.getUserId());
        List<SysRole> roles = roleMapper.selectListByUserId(loginUser.getUserId());
        SysDept dept = deptMapper.selectById(user.getDeptId());
        return new SysUserProfileVO()
                .setId(user.getId())
                .setNickname(user.getNickname())
                .setUsername(user.getUsername())
                .setDeptId(dept.getId())
                .setDeptName(dept.getDeptName())
                .setLastLoginIp(user.getLastLoginIp())
                .setLastLoginTime(user.getLastLoginTime())
                .setRoleNames(CollUtil.map(roles, SysRole::getRoleName, true));
    }

    @Override
    public boolean updateUserLoginInfo(UserLoginDTO userLoginDTO) {
        SysUser user = validateExistence(userLoginDTO.getId());
        user.setLastLoginIp(userLoginDTO.getLastLoginIp()).setLastLoginTime(userLoginDTO.getLastLoginTime());
        return updateById(user);
    }

    @Override
    public boolean modifyUserProfile(SysUserProfileDTO dto) {
        LoginUser loginUser = SecurityUtil.getLoginUser();
        SysUser user = validateExistence(loginUser.getUserId());
        user.setNickname(dto.getNickname());
        return updateById(user);
    }

    @Override
    public boolean resetPassword(SysUserRestPasswordDTO dto) {
        LoginUser loginUser = SecurityUtil.getLoginUser();
        SysUser user = validateExistence(loginUser.getUserId());
        boolean matched = passwordEncoder.matches(dto.getOldPassword(), user.getPassword());
        Assert.isTrue(matched, () -> BizException.of(SystemConstant.PASSWORD_NOT_MATCHED));
        user.setPassword(passwordEncoder.encode(dto.getNewPassword()));
        return updateById(user);
    }

    @Override
    public List<SysUserVO> list(SysUserListDTO dto) {
        List<SysUser> list = baseMapper.selectList(lambdaQuery().getWrapper()
                .eq(dto.getDeptId() != null, SysUser::getDeptId, dto.getDeptId())
                .eq(dto.getUserStatus() != null, SysUser::getUserStatus, dto.getUserStatus()));
        return CollUtil.map(list, baseConvert::toVO, true);
    }

    @Override
    public SysUser validateExistence(Long id) {
        SysUser sysUser = baseMapper.selectById(id);
        Assert.notNull(sysUser, () -> BizException.of(SystemConstant.USER_NOT_FOUND));
        return sysUser;
    }

    @Override
    public void validateUniqueness(SysUser user) {
        boolean existed = baseMapper.exists(lambdaQuery().getWrapper()
                .eq(SysUser::getUsername, user.getUsername())
                .ne(user.getId() != null, SysUser::getId, user.getId()));
        Assert.isFalse(existed, () -> BizException.of(SystemConstant.USER_EXISTED));
    }
}
